#include "Game.h"
#include "ModelImporter.h"
#include <iostream>
#include <vector>
#include <crtdbg.h>
#include "GPUSimulator.h"

#define __DRAW

static const char g_simpleEffectSrc[] =
        "matrix g_mWorld;\n" \
        "matrix g_mView;\n" \
        "matrix g_mProjection;\n" \
        "\n" \
        "struct Fragment{ \n" \
        "    float4 Pos : SV_POSITION;\n" \
        "    float4 Col : TEXCOORD0; };\n" \
        "\n" \
        "Fragment VS( float4 Pos : POSITION, float4 Col : COLOR )\n" \
        "{\n" \
        "    Fragment f;\n" \
        "    f.Pos = mul(Pos, g_mWorld);\n" \
        "    f.Pos = mul(f.Pos, g_mView);\n" \
        "    f.Pos = mul(f.Pos, g_mProjection);\n" \
        "    f.Col = float4(1, 1, 1, 1);\n" \
        "    return f;\n" \
        "}\n" \
        "\n" \
        "float4 PS( Fragment f ) : SV_Target\n" \
        "{\n" \
        "    return f.Col;\n" \
        "}\n" \
        "\n" \
        "technique10 Render\n" \
        "{\n" \
        "    pass P0\n" \
        "    {\n" \
        "        SetVertexShader( CompileShader( vs_4_0, VS() ) );\n" \
        "        SetGeometryShader( NULL );\n" \
        "        SetPixelShader( CompileShader( ps_4_0, PS() ) );\n" \
        "    }\n" \
        "}\n" \
        "\n";


using namespace std;

Game::Game(void)
{
    device				= NULL; // Our rendering device
    pSwapChain        = NULL; // The swap chain of the window
    pSwapChainRTV     = NULL; // The Render target view on the swap chain ( used for clear)

    pInputLayout      = NULL;
    pSimpleEffect     = NULL;
    pSimpleTechnique  = NULL;
    pmWorld = NULL;
    pmView = NULL;
    pmProjection      = NULL;

    Width = Settings::width;
    Height = Settings::height;
}

Game::~Game(void)
{
}

void Game::Run()
{

    //return DXUTGetExitCode();
    //bool bCheckD3D10 = dynlinkLoadD3D10API();

    //// If D3D10 is not present, print an error message and then quit
    //if (!bCheckD3D10) {
    //    printf("The program did not detect a D3D10 device, exiting...\n");
    //    dynlinkUnloadD3D10API();
    //}

    //Initialize();

    //// Enter the message loop
    //MSG msg;
    //ZeroMemory( &msg, sizeof(msg) );
    //while( msg.message!=WM_QUIT )
    //{
    //    if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
    //    {
    //        TranslateMessage( &msg );
    //        DispatchMessage( &msg );
    //    }
    //    else
    //    {
    //        float dt = 0.016f;
    //        Update(dt);
    //        Draw(dt);
    //    }
    //}

    //UnregisterClass( wc.lpszClassName, wc.hInstance );

}

void Game::ResizeSwapChain(ID3D10Device* pd3dDevice, IDXGISwapChain* pSwapChain,
						   const DXGI_SURFACE_DESC* pBufferSurfaceDesc, void* pUserContext )
{
	// Si ya creamos el RenderTargetView retornamos.
	if (pSwapChainRTV != NULL)
		return;
	HRESULT hr = S_OK;

	this->pSwapChain = pSwapChain;
	// Create a render target view of the swapchain
	ID3D10Texture2D* pBuffer;
	hr = pSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ), (LPVOID*)&pBuffer);
	SUCCEEDED(hr);

	hr = device->CreateRenderTargetView(pBuffer, NULL, &pSwapChainRTV);
	SUCCEEDED(hr);
	pBuffer->Release();

	device->OMSetRenderTargets(1, &pSwapChainRTV, NULL);
}


void Game::Initialize()
{
    InitGeometry();
    InitCuda();
    InitD3D();

	ModelImporter importer = ModelImporter();
	Model<Vector4> *model = importer.Import("Content\\Plane01");

	this->simulator = (Simulator *) new GPUSimulator(device);
	this->simulator->SetModel(model);
}

HRESULT Game::InitD3D()
{
    //HRESULT hr = S_OK;
    //cudaError cuStatus;

    //// Select our adapter
    //IDXGIAdapter* pCudaCapableAdapter = NULL;
    //{
    //    // iterate through the candidate adapters
    //    IDXGIFactory *pFactory;
    //    hr = sFnPtr_CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)(&pFactory) );
    //    AssertOrQuit( SUCCEEDED(hr) );

    //    for (UINT adapter = 0; !pCudaCapableAdapter; ++adapter)
    //    {
    //        // get a candidate DXGI adapter
    //        IDXGIAdapter* pAdapter = NULL;
    //        hr = pFactory->EnumAdapters(adapter, &pAdapter);
    //        if (FAILED(hr))
    //        {
    //            break;
    //        }
    //        // query to see if there exists a corresponding compute device
    //        int cuDevice;
    //        cuStatus = cudaD3D10GetDevice(&cuDevice, pAdapter);
    //        if (cudaSuccess == cuStatus)
    //        {
    //            // if so, mark it as the one against which to create our d3d10 device
    //            pCudaCapableAdapter = pAdapter;
    //            pCudaCapableAdapter->AddRef();
    //        }
    //        pAdapter->Release();
    //    }
    //    pFactory->Release();
    //    // clear any errors we got while querying invalid compute devices
    //    cuStatus = cudaGetLastError();
    //}
    //AssertOrQuit(pCudaCapableAdapter);

    //// Set up the structure used to create the device and swapchain
    //DXGI_SWAP_CHAIN_DESC sd;
    //ZeroMemory( &sd, sizeof(sd) );
    //sd.BufferCount = 1;
    //sd.BufferDesc.Width = Width;
    //sd.BufferDesc.Height = Height;
    //sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    //sd.BufferDesc.RefreshRate.Numerator = 60;
    //sd.BufferDesc.RefreshRate.Denominator = 1;
    //sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    //sd.OutputWindow = hWnd;
    //sd.SampleDesc.Count = 1;
    //sd.SampleDesc.Quality = 0;
    //sd.Windowed = TRUE;

    //// Create device and swapchain
    //hr = sFnPtr_D3D10CreateDeviceAndSwapChain( 
    //    pCudaCapableAdapter, 
    //    D3D10_DRIVER_TYPE_HARDWARE, 
    //    NULL, 
    //    D3D10_CREATE_DEVICE_DEBUG,
    //    D3D10_SDK_VERSION, 
    //    &sd, 
    //    &g_pSwapChain, 
    //    &device);
    //AssertOrQuit(SUCCEEDED(hr));
    //pCudaCapableAdapter->Release();

    //// Create a render target view of the swapchain
    //ID3D10Texture2D* pBuffer;
    //hr = g_pSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ), (LPVOID*)&pBuffer);
    //AssertOrQuit(SUCCEEDED(hr));

    //hr = device->CreateRenderTargetView(pBuffer, NULL, &g_pSwapChainRTV);
    //AssertOrQuit(SUCCEEDED(hr));
    //pBuffer->Release();

    //device->OMSetRenderTargets(1, &g_pSwapChainRTV, NULL);

    //// Setup the viewport
    //D3D10_VIEWPORT vp;
    //vp.Width = Width;
    //vp.Height = Height;
    //vp.MinDepth = 0.0f;
    //vp.MaxDepth = 1.0f;
    //vp.TopLeftX = 0;
    //vp.TopLeftY = 0;
    //device->RSSetViewports( 1, &vp );

    HRESULT hr = S_OK;
    // Setup the effect
    {
        ID3D10Blob* pCompiledEffect;
        hr = D3D10CompileEffectFromMemory(
            (void*)g_simpleEffectSrc,
            sizeof(g_simpleEffectSrc),
            NULL,
            NULL, // pDefines
            NULL, // pIncludes
            0, // HLSL flags
            0, // FXFlags
            &pCompiledEffect,
            NULL);
        AssertOrQuit(SUCCEEDED(hr));

        hr = D3D10CreateEffectFromMemory(
            pCompiledEffect->GetBufferPointer(),
            pCompiledEffect->GetBufferSize(),
            0, // FXFlags
            device,
            NULL,
            &pSimpleEffect);
        pCompiledEffect->Release();

        pSimpleTechnique = pSimpleEffect->GetTechniqueByName( "Render" );

        //    g_pmWorldViewProjection = g_pSimpleEffect->GetVariableByName("g_mWorldViewProjection")->AsMatrix();
        pmWorld = pSimpleEffect->GetVariableByName("g_mWorld")->AsMatrix();
        pmView = pSimpleEffect->GetVariableByName("g_mView")->AsMatrix();
        pmProjection = pSimpleEffect->GetVariableByName("g_mProjection")->AsMatrix();

        // Define the input layout
        D3D10_INPUT_ELEMENT_DESC layout[] =
        {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },  
            { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },  
        };
        UINT numElements = sizeof(layout)/sizeof(layout[0]);

        // Create the input layout
        D3D10_PASS_DESC PassDesc;
        pSimpleTechnique->GetPassByIndex(0)->GetDesc( &PassDesc );
        hr = device->CreateInputLayout(layout, numElements, PassDesc.pIAInputSignature, PassDesc.IAInputSignatureSize, &pInputLayout );
        AssertOrQuit(SUCCEEDED(hr));

        // Setup Input Layout, apply effect and draw points
        device->IASetInputLayout(pInputLayout);
        pSimpleTechnique->GetPassByIndex(0)->Apply(0);
        device->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_POINTLIST);

        ID3D10RasterizerState * g_pRasterState;

        D3D10_RASTERIZER_DESC rasterizerState;
        rasterizerState.FillMode = D3D10_FILL_SOLID;
        rasterizerState.CullMode = D3D10_CULL_NONE;
        rasterizerState.FrontCounterClockwise = true;
        rasterizerState.DepthBias = false;
        rasterizerState.DepthBiasClamp = 0;
        rasterizerState.SlopeScaledDepthBias = 0;
        rasterizerState.DepthClipEnable = true;
        rasterizerState.ScissorEnable = true;
        rasterizerState.MultisampleEnable = false;
        rasterizerState.AntialiasedLineEnable = false;
        device->CreateRasterizerState( &rasterizerState, &g_pRasterState );
        device->RSSetState(g_pRasterState);


    }

    //

    return S_OK;
}

HRESULT Game::InitGeometry()
{
	return S_OK;
}



void Game::InitCuda()
{
    
}

void Game::Update(float dt)
{
    while (true)
	{
		simulator->Step(dt);
			Draw(dt);
	}
}

void Game::Draw(float dt)
{
    // Clear the backbuffer to a black color
    float ClearColor[4] = {0.0f, 0.0f, 0.0f, 1.0f};
    device->ClearRenderTargetView( pSwapChainRTV, ClearColor);


    // Draw frame
    {
        // Setup the world, view, and projection matrices
        D3DXMATRIXA16  matWorld;
        D3DXMatrixIdentity(&matWorld);

        D3DXVECTOR3 vEyePt( 6.0f, 1.0f, 6.0f );
        D3DXVECTOR3 vLookatPt( 0.0f, -2.0f, 0.0f );
        D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
        D3DXMATRIXA16  matView;
        D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );

        D3DXMATRIXA16 matProj;
        D3DXMatrixPerspectiveFovLH( &matProj, (float)D3DX_PI/4.0f, 1280.0f/720.0f, 0.01f, 50.0f );

        pmWorld->SetMatrix(( float* ) &matWorld);
        pmView->SetMatrix(( float* ) &matView);
        pmProjection->SetMatrix(( float* ) &matProj);

        // Render the vertex buffer contents
        UINT stride = sizeof(Vector4);
        UINT offset = 0;
		ID3D10Buffer *vertices = simulator->GetVertexBuffer();
        device->IASetVertexBuffers( 0, 1, &vertices, &stride, &offset );
        //device->IASetIndexBuffer(pIndices, DXGI_FORMAT_R32_UINT, 0);
        device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_POINTLIST);
        //device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_LINELIST);
        //device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

        pSimpleTechnique->GetPassByIndex(0)->Apply(0);
        device->Draw(Settings::particleCount, 0 );
        //device->DrawIndexed(Settings::faceCount * 3, 0, 0);

    }

    // Present the backbuffer contents to the display
    pSwapChain->Present( 0, 0);

    dt += 0.01f;
}

void Game::OnMouseMove(int x, int y)
{
}

void Game::OnMouseLeftDown(int x, int y)
{
}

void Game::OnMouseLeftUp(int x, int y)
{
}

void Game::OnMouseWheel(int x, int y, int delta)
{
}

void Game::Dispose()
{
    
    
}